package br.com.simtecnologia.access.controll.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PreDestroy;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.Intercepts;
import br.com.caelum.vraptor.core.InterceptorStack;
import br.com.caelum.vraptor.core.Localization;
import br.com.caelum.vraptor.core.MethodInfo;
import br.com.caelum.vraptor.http.ParametersProvider;
import br.com.caelum.vraptor.interceptor.Interceptor;
import br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor;
import br.com.caelum.vraptor.ioc.Container;
import br.com.caelum.vraptor.ioc.RequestScoped;
import br.com.caelum.vraptor.resource.ResourceMethod;
import br.com.caelum.vraptor.validator.Message;
import br.com.simtecnologia.access.controll.AccessLogicRouter;
import br.com.simtecnologia.access.controll.DefaultLoginSession;
import br.com.simtecnologia.access.controll.LoginSession;
import br.com.simtecnologia.access.controll.annotation.ControlledLogic;
import br.com.simtecnologia.access.controll.annotation.ControlledResource;
import br.com.simtecnologia.access.controll.annotation.DeniedLogic;
import br.com.simtecnologia.access.controll.annotation.LoginLogic;

@Intercepts
@RequestScoped
public class AccessInterceptor implements Interceptor, Serializable {
	private static final Logger LOGGER = LoggerFactory.getLogger(AccessInterceptor.class);
	private static void debug(String regex, Object ... objects) {
		if (LOGGER.isDebugEnabled())
			LOGGER.debug(String.format(regex, objects));
	}
		
	private static final long serialVersionUID = -1581456897689494695L;
	private final Container container;
	private final DefaultLoginFlow defaultLoginFlow;
	private final HttpSession session;
    private final ParametersProvider provider;
    private final Localization localization;
	private final List<Message> errors = new ArrayList<Message>();
	private final ResourceMethod method;
	
	private ControlledLogic logica;
	private ControlledResource recurso;
	private Class<? extends LoginSession> sessionClass;
	private LoginSession loginSession;

	public AccessInterceptor(final Container container, DefaultLoginFlow defaultLoginFlow,
			MethodInfo methodInfo, HttpSession session, ParametersProvider provider, Localization localization) {
		this.container = container;
		this.defaultLoginFlow = defaultLoginFlow;
		this.method = methodInfo.getResourceMethod();
		this.session = session;
		this.provider = provider;
		this.localization = localization;
		defaultLoginFlow.setContainer(container);
		
	}
	
	@PreDestroy
	public void reset() {
		logica = null;
		recurso = null;
	}
	

	@Override
	public boolean accepts(ResourceMethod method) {
		if (method.containsAnnotation(LoginLogic.class) || method.containsAnnotation(DeniedLogic.class))
			return false;
		if (method.containsAnnotation(ControlledLogic.class) || method.getResource().getType().isAnnotationPresent(ControlledResource.class)) {
			logica = method.getMethod().getAnnotation(ControlledLogic.class);
			recurso = method.getResource().getType().getAnnotation(ControlledResource.class);
			boolean accepts = logica != null ? logica.locked() : recurso.locked();
			if (accepts) {
				sessionClass = recurso == null ? DefaultLoginSession.class : recurso.sessionClass();
				debug("Found a controlled logic: [%s]",method);
			}
			return accepts;
		}
		return false;
	}
	
	private LoginSession getLoginSession() {
		if (loginSession == null)
			loginSession = container.instanceFor(sessionClass);
		return loginSession;
	}

	@Override
	public void intercept(InterceptorStack stack, ResourceMethod method,
			Object resourceInstance) throws InterceptionException {

		LoginSession loginSession = getLoginSession();
		if (!loginSession.isLogged()) {
			redirectToLoginPage(loginSession);
		} else if (!loginSession.hasAccess(method))
			redirectToDeniedPage();
		else {
			debug("Access allowed to user [%s]",loginSession);
			defaultLoginFlow.unlock(loginSession);
			stack.next(method, resourceInstance);
		}
	}

	private void redirectToDeniedPage() {
		debug("Access denied for this user [%s] on the [%s] session",getLoginSession(),sessionClass.getName());
		AccessLogicRouter router = container.instanceFor(AccessLogicRouter.class);
		router.redirectToDeniedPage();
		
	}

	private void redirectToLoginPage(LoginSession loginSession) {
		debug("The user is not logged at [%s] session",sessionClass.getName());
		AccessLogicRouter router = container.instanceFor(AccessLogicRouter.class);
		long loginFlowId = defaultLoginFlow.addLoginRequest(loginSession,method,getParametersFor(method));
//		if (!result.included().containsKey("loginFlowId"))
//			result.include("loginFlowId", loginFlowId);
		router.redirectToLoginPage(loginFlowId);
	}
	
	private Object[] getParametersFor(ResourceMethod method) {
		Object[] args = (Object[]) session.getAttribute(ParametersInstantiatorInterceptor.FLASH_PARAMETERS);
		if (args == null) {
			return provider.getParametersFor(method, errors, localization.getBundle());
		}
		return args;
	}
}
